home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / lib / ruby / 1.8 / fileutils.rb < prev    next >
Encoding:
Ruby Source  |  2010-06-08  |  42.1 KB  |  1,594 lines

  1. # = fileutils.rb
  2. # Copyright (c) 2000-2006 Minero Aoki
  3. # This program is free software.
  4. # You can distribute/modify this program under the same terms of ruby.
  5. # == module FileUtils
  6. # Namespace for several file utility methods for copying, moving, removing, etc.
  7. # === Module Functions
  8. #   cd(dir, options)
  9. #   cd(dir, options) {|dir| .... }
  10. #   pwd()
  11. #   mkdir(dir, options)
  12. #   mkdir(list, options)
  13. #   mkdir_p(dir, options)
  14. #   mkdir_p(list, options)
  15. #   rmdir(dir, options)
  16. #   rmdir(list, options)
  17. #   ln(old, new, options)
  18. #   ln(list, destdir, options)
  19. #   ln_s(old, new, options)
  20. #   ln_s(list, destdir, options)
  21. #   ln_sf(src, dest, options)
  22. #   cp(src, dest, options)
  23. #   cp(list, dir, options)
  24. #   cp_r(src, dest, options)
  25. #   cp_r(list, dir, options)
  26. #   mv(src, dest, options)
  27. #   mv(list, dir, options)
  28. #   rm(list, options)
  29. #   rm_r(list, options)
  30. #   rm_rf(list, options)
  31. #   install(src, dest, mode = <src's>, options)
  32. #   chmod(mode, list, options)
  33. #   chmod_R(mode, list, options)
  34. #   chown(user, group, list, options)
  35. #   chown_R(user, group, list, options)
  36. #   touch(list, options)
  37. #
  38. # The <tt>options</tt> parameter is a hash of options, taken from the list
  39. # <tt>:force</tt>, <tt>:noop</tt>, <tt>:preserve</tt>, and <tt>:verbose</tt>.
  40. # <tt>:noop</tt> means that no changes are made.  The other two are obvious.
  41. # Each method documents the options that it honours.
  42. #
  43. # All methods that have the concept of a "source" file or directory can take
  44. # either one file or a list of files in that argument.  See the method
  45. # documentation for examples.
  46. #
  47. # There are some `low level' methods, which do not accept any option:
  48. #
  49. #   copy_entry(src, dest, preserve = false, dereference = false)
  50. #   copy_file(src, dest, preserve = false, dereference = true)
  51. #   copy_stream(srcstream, deststream)
  52. #   remove_entry(path, force = false)
  53. #   remove_entry_secure(path, force = false)
  54. #   remove_file(path, force = false)
  55. #   compare_file(path_a, path_b)
  56. #   compare_stream(stream_a, stream_b)
  57. #   uptodate?(file, cmp_list)
  58. #
  59. # == module FileUtils::Verbose
  60. # This module has all methods of FileUtils module, but it outputs messages
  61. # before acting.  This equates to passing the <tt>:verbose</tt> flag to methods
  62. # in FileUtils.
  63. # == module FileUtils::NoWrite
  64. # This module has all methods of FileUtils module, but never changes
  65. # files/directories.  This equates to passing the <tt>:noop</tt> flag to methods
  66. # in FileUtils.
  67. # == module FileUtils::DryRun
  68. # This module has all methods of FileUtils module, but never changes
  69. # files/directories.  This equates to passing the <tt>:noop</tt> and
  70. # <tt>:verbose</tt> flags to methods in FileUtils.
  71.  
  72. module FileUtils
  73.  
  74.   def self.private_module_function(name)   #:nodoc:
  75.     module_function name
  76.     private_class_method name
  77.   end
  78.  
  79.   # This hash table holds command options.
  80.   OPT_TABLE = {}   #:nodoc: internal use only
  81.  
  82.   #
  83.   # Options: (none)
  84.   #
  85.   # Returns the name of the current directory.
  86.   #
  87.   def pwd
  88.     Dir.pwd
  89.   end
  90.   module_function :pwd
  91.  
  92.   alias getwd pwd
  93.   module_function :getwd
  94.  
  95.   #
  96.   # Options: verbose
  97.   # 
  98.   # Changes the current directory to the directory +dir+.
  99.   # 
  100.   # If this method is called with block, resumes to the old
  101.   # working directory after the block execution finished.
  102.   # 
  103.   #   FileUtils.cd('/', :verbose => true)   # chdir and report it
  104.   # 
  105.   def cd(dir, options = {}, &block) # :yield: dir
  106.     fu_check_options options, OPT_TABLE['cd']
  107.     fu_output_message "cd #{dir}" if options[:verbose]
  108.     Dir.chdir(dir, &block)
  109.     fu_output_message 'cd -' if options[:verbose] and block
  110.   end
  111.   module_function :cd
  112.  
  113.   alias chdir cd
  114.   module_function :chdir
  115.  
  116.   OPT_TABLE['cd']    =
  117.   OPT_TABLE['chdir'] = [:verbose]
  118.  
  119.   #
  120.   # Options: (none)
  121.   # 
  122.   # Returns true if +newer+ is newer than all +old_list+.
  123.   # Non-existent files are older than any file.
  124.   # 
  125.   #   FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \
  126.   #       system 'make hello.o'
  127.   # 
  128.   def uptodate?(new, old_list, options = nil)
  129.     raise ArgumentError, 'uptodate? does not accept any option' if options
  130.  
  131.     return false unless File.exist?(new)
  132.     new_time = File.mtime(new)
  133.     old_list.each do |old|
  134.       if File.exist?(old)
  135.         return false unless new_time > File.mtime(old)
  136.       end
  137.     end
  138.     true
  139.   end
  140.   module_function :uptodate?
  141.  
  142.   #
  143.   # Options: mode noop verbose
  144.   # 
  145.   # Creates one or more directories.
  146.   # 
  147.   #   FileUtils.mkdir 'test'
  148.   #   FileUtils.mkdir %w( tmp data )
  149.   #   FileUtils.mkdir 'notexist', :noop => true  # Does not really create.
  150.   #   FileUtils.mkdir 'tmp', :mode => 0700
  151.   # 
  152.   def mkdir(list, options = {})
  153.     fu_check_options options, OPT_TABLE['mkdir']
  154.     list = fu_list(list)
  155.     fu_output_message "mkdir #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose]
  156.     return if options[:noop]
  157.  
  158.     list.each do |dir|
  159.       fu_mkdir dir, options[:mode]
  160.     end
  161.   end
  162.   module_function :mkdir
  163.  
  164.   OPT_TABLE['mkdir'] = [:mode, :noop, :verbose]
  165.  
  166.   #
  167.   # Options: mode noop verbose
  168.   # 
  169.   # Creates a directory and all its parent directories.
  170.   # For example,
  171.   # 
  172.   #   FileUtils.mkdir_p '/usr/local/lib/ruby'
  173.   # 
  174.   # causes to make following directories, if it does not exist.
  175.   #     * /usr
  176.   #     * /usr/local
  177.   #     * /usr/local/lib
  178.   #     * /usr/local/lib/ruby
  179.   #
  180.   # You can pass several directories at a time in a list.
  181.   # 
  182.   def mkdir_p(list, options = {})
  183.     fu_check_options options, OPT_TABLE['mkdir_p']
  184.     list = fu_list(list)
  185.     fu_output_message "mkdir -p #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose]
  186.     return *list if options[:noop]
  187.  
  188.     list.map {|path| path.sub(%r</\z>, '') }.each do |path|
  189.       # optimize for the most common case
  190.       begin
  191.         fu_mkdir path, options[:mode]
  192.         next
  193.       rescue SystemCallError
  194.         next if File.directory?(path)
  195.       end
  196.  
  197.       stack = []
  198.       until path == stack.last   # dirname("/")=="/", dirname("C:/")=="C:/"
  199.         stack.push path
  200.         path = File.dirname(path)
  201.       end
  202.       stack.reverse_each do |path|
  203.         begin
  204.           fu_mkdir path, options[:mode]
  205.         rescue SystemCallError => err
  206.           raise unless File.directory?(path)
  207.         end
  208.       end
  209.     end
  210.  
  211.     return *list
  212.   end
  213.   module_function :mkdir_p
  214.  
  215.   alias mkpath    mkdir_p
  216.   alias makedirs  mkdir_p
  217.   module_function :mkpath
  218.   module_function :makedirs
  219.  
  220.   OPT_TABLE['mkdir_p']  =
  221.   OPT_TABLE['mkpath']   =
  222.   OPT_TABLE['makedirs'] = [:mode, :noop, :verbose]
  223.  
  224.   def fu_mkdir(path, mode)   #:nodoc:
  225.     path = path.sub(%r</\z>, '')
  226.     if mode
  227.       Dir.mkdir path, mode
  228.       File.chmod mode, path
  229.     else
  230.       Dir.mkdir path
  231.     end
  232.   end
  233.   private_module_function :fu_mkdir
  234.  
  235.   #
  236.   # Options: noop, verbose
  237.   # 
  238.   # Removes one or more directories.
  239.   # 
  240.   #   FileUtils.rmdir 'somedir'
  241.   #   FileUtils.rmdir %w(somedir anydir otherdir)
  242.   #   # Does not really remove directory; outputs message.
  243.   #   FileUtils.rmdir 'somedir', :verbose => true, :noop => true
  244.   # 
  245.   def rmdir(list, options = {})
  246.     fu_check_options options, OPT_TABLE['rmdir']
  247.     list = fu_list(list)
  248.     fu_output_message "rmdir #{list.join ' '}" if options[:verbose]
  249.     return if options[:noop]
  250.     list.each do |dir|
  251.       Dir.rmdir dir.sub(%r</\z>, '')
  252.     end
  253.   end
  254.   module_function :rmdir
  255.  
  256.   OPT_TABLE['rmdir'] = [:noop, :verbose]
  257.  
  258.   #
  259.   # Options: force noop verbose
  260.   #
  261.   # <b><tt>ln(old, new, options = {})</tt></b>
  262.   #
  263.   # Creates a hard link +new+ which points to +old+.
  264.   # If +new+ already exists and it is a directory, creates a link +new/old+.
  265.   # If +new+ already exists and it is not a directory, raises Errno::EEXIST.
  266.   # But if :force option is set, overwrite +new+.
  267.   # 
  268.   #   FileUtils.ln 'gcc', 'cc', :verbose => true
  269.   #   FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs'
  270.   # 
  271.   # <b><tt>ln(list, destdir, options = {})</tt></b>
  272.   # 
  273.   # Creates several hard links in a directory, with each one pointing to the
  274.   # item in +list+.  If +destdir+ is not a directory, raises Errno::ENOTDIR.
  275.   # 
  276.   #   include FileUtils
  277.   #   cd '/sbin'
  278.   #   FileUtils.ln %w(cp mv mkdir), '/bin'   # Now /sbin/cp and /bin/cp are linked.
  279.   # 
  280.   def ln(src, dest, options = {})
  281.     fu_check_options options, OPT_TABLE['ln']
  282.     fu_output_message "ln#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
  283.     return if options[:noop]
  284.     fu_each_src_dest0(src, dest) do |s,d|
  285.       remove_file d, true if options[:force]
  286.       File.link s, d
  287.     end
  288.   end
  289.   module_function :ln
  290.  
  291.   alias link ln
  292.   module_function :link
  293.  
  294.   OPT_TABLE['ln']   =
  295.   OPT_TABLE['link'] = [:force, :noop, :verbose]
  296.  
  297.   #
  298.   # Options: force noop verbose
  299.   #
  300.   # <b><tt>ln_s(old, new, options = {})</tt></b>
  301.   # 
  302.   # Creates a symbolic link +new+ which points to +old+.  If +new+ already
  303.   # exists and it is a directory, creates a symbolic link +new/old+.  If +new+
  304.   # already exists and it is not a directory, raises Errno::EEXIST.  But if
  305.   # :force option is set, overwrite +new+.
  306.   # 
  307.   #   FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby'
  308.   #   FileUtils.ln_s 'verylongsourcefilename.c', 'c', :force => true
  309.   # 
  310.   # <b><tt>ln_s(list, destdir, options = {})</tt></b>
  311.   # 
  312.   # Creates several symbolic links in a directory, with each one pointing to the
  313.   # item in +list+.  If +destdir+ is not a directory, raises Errno::ENOTDIR.
  314.   #
  315.   # If +destdir+ is not a directory, raises Errno::ENOTDIR.
  316.   # 
  317.   #   FileUtils.ln_s Dir.glob('bin/*.rb'), '/home/aamine/bin'
  318.   # 
  319.   def ln_s(src, dest, options = {})
  320.     fu_check_options options, OPT_TABLE['ln_s']
  321.     fu_output_message "ln -s#{options[:force] ? 'f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
  322.     return if options[:noop]
  323.     fu_each_src_dest0(src, dest) do |s,d|
  324.       remove_file d, true if options[:force]
  325.       File.symlink s, d
  326.     end
  327.   end
  328.   module_function :ln_s
  329.  
  330.   alias symlink ln_s
  331.   module_function :symlink
  332.  
  333.   OPT_TABLE['ln_s']    =
  334.   OPT_TABLE['symlink'] = [:force, :noop, :verbose]
  335.  
  336.   #
  337.   # Options: noop verbose
  338.   # 
  339.   # Same as
  340.   #   #ln_s(src, dest, :force)
  341.   # 
  342.   def ln_sf(src, dest, options = {})
  343.     fu_check_options options, OPT_TABLE['ln_sf']
  344.     options = options.dup
  345.     options[:force] = true
  346.     ln_s src, dest, options
  347.   end
  348.   module_function :ln_sf
  349.  
  350.   OPT_TABLE['ln_sf'] = [:noop, :verbose]
  351.  
  352.   #
  353.   # Options: preserve noop verbose
  354.   #
  355.   # Copies a file content +src+ to +dest+.  If +dest+ is a directory,
  356.   # copies +src+ to +dest/src+.
  357.   #
  358.   # If +src+ is a list of files, then +dest+ must be a directory.
  359.   #
  360.   #   FileUtils.cp 'eval.c', 'eval.c.org'
  361.   #   FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6'
  362.   #   FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', :verbose => true
  363.   #   FileUtils.cp 'symlink', 'dest'   # copy content, "dest" is not a symlink
  364.   # 
  365.   def cp(src, dest, options = {})
  366.     fu_check_options options, OPT_TABLE['cp']
  367.     fu_output_message "cp#{options[:preserve] ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
  368.     return if options[:noop]
  369.     fu_each_src_dest(src, dest) do |s, d|
  370.       copy_file s, d, options[:preserve]
  371.     end
  372.   end
  373.   module_function :cp
  374.  
  375.   alias copy cp
  376.   module_function :copy
  377.  
  378.   OPT_TABLE['cp']   =
  379.   OPT_TABLE['copy'] = [:preserve, :noop, :verbose]
  380.  
  381.   #
  382.   # Options: preserve noop verbose dereference_root remove_destination
  383.   # 
  384.   # Copies +src+ to +dest+. If +src+ is a directory, this method copies
  385.   # all its contents recursively. If +dest+ is a directory, copies
  386.   # +src+ to +dest/src+.
  387.   #
  388.   # +src+ can be a list of files.
  389.   # 
  390.   #   # Installing ruby library "mylib" under the site_ruby
  391.   #   FileUtils.rm_r site_ruby + '/mylib', :force
  392.   #   FileUtils.cp_r 'lib/', site_ruby + '/mylib'
  393.   # 
  394.   #   # Examples of copying several files to target directory.
  395.   #   FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail'
  396.   #   FileUtils.cp_r Dir.glob('*.rb'), '/home/aamine/lib/ruby', :noop => true, :verbose => true
  397.   #
  398.   #   # If you want to copy all contents of a directory instead of the
  399.   #   # directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
  400.   #   # use following code.
  401.   #   FileUtils.cp_r 'src/.', 'dest'     # cp_r('src', 'dest') makes src/dest,
  402.   #                                      # but this doesn't.
  403.   # 
  404.   def cp_r(src, dest, options = {})
  405.     fu_check_options options, OPT_TABLE['cp_r']
  406.     fu_output_message "cp -r#{options[:preserve] ? 'p' : ''}#{options[:remove_destination] ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
  407.     return if options[:noop]
  408.     options = options.dup
  409.     options[:dereference_root] = true unless options.key?(:dereference_root)
  410.     fu_each_src_dest(src, dest) do |s, d|
  411.       copy_entry s, d, options[:preserve], options[:dereference_root], options[:remove_destination]
  412.     end
  413.   end
  414.   module_function :cp_r
  415.  
  416.   OPT_TABLE['cp_r'] = [:preserve, :noop, :verbose,
  417.                        :dereference_root, :remove_destination]
  418.  
  419.   #
  420.   # Copies a file system entry +src+ to +dest+.
  421.   # If +src+ is a directory, this method copies its contents recursively.
  422.   # This method preserves file types, c.f. symlink, directory...
  423.   # (FIFO, device files and etc. are not supported yet)
  424.   #
  425.   # Both of +src+ and +dest+ must be a path name.
  426.   # +src+ must exist, +dest+ must not exist.
  427.   #
  428.   # If +preserve+ is true, this method preserves owner, group, permissions
  429.   # and modified time.
  430.   #
  431.   # If +dereference_root+ is true, this method dereference tree root.
  432.   #
  433.   # If +remove_destination+ is true, this method removes each destination file before copy.
  434.   #
  435.   def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
  436.     Entry_.new(src, nil, dereference_root).traverse do |ent|
  437.       destent = Entry_.new(dest, ent.rel, false)
  438.       File.unlink destent.path if remove_destination && File.file?(destent.path)
  439.       ent.copy destent.path
  440.       ent.copy_metadata destent.path if preserve
  441.     end
  442.   end
  443.   module_function :copy_entry
  444.  
  445.   #
  446.   # Copies file contents of +src+ to +dest+.
  447.   # Both of +src+ and +dest+ must be a path name.
  448.   #
  449.   def copy_file(src, dest, preserve = false, dereference = true)
  450.     ent = Entry_.new(src, nil, dereference)
  451.     ent.copy_file dest
  452.     ent.copy_metadata dest if preserve
  453.   end
  454.   module_function :copy_file
  455.  
  456.   #
  457.   # Copies stream +src+ to +dest+.
  458.   # +src+ must respond to #read(n) and
  459.   # +dest+ must respond to #write(str).
  460.   #
  461.   def copy_stream(src, dest)
  462.     fu_copy_stream0 src, dest, fu_stream_blksize(src, dest)
  463.   end
  464.   module_function :copy_stream
  465.  
  466.   #
  467.   # Options: force noop verbose
  468.   # 
  469.   # Moves file(s) +src+ to +dest+.  If +file+ and +dest+ exist on the different
  470.   # disk partition, the file is copied instead.
  471.   # 
  472.   #   FileUtils.mv 'badname.rb', 'goodname.rb'
  473.   #   FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', :force => true  # no error
  474.   # 
  475.   #   FileUtils.mv %w(junk.txt dust.txt), '/home/aamine/.trash/'
  476.   #   FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true
  477.   # 
  478.   def mv(src, dest, options = {})
  479.     fu_check_options options, OPT_TABLE['mv']
  480.     fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
  481.     return if options[:noop]
  482.     fu_each_src_dest(src, dest) do |s, d|
  483.       destent = Entry_.new(d, nil, true)
  484.       begin
  485.         if destent.exist?
  486.           if destent.directory?
  487.             raise Errno::EEXIST, dest
  488.           else
  489.             destent.remove_file if rename_cannot_overwrite_file?
  490.           end
  491.         end
  492.         begin
  493.           File.rename s, d
  494.         rescue Errno::EXDEV
  495.           copy_entry s, d, true
  496.           if options[:secure]
  497.             remove_entry_secure s, options[:force]
  498.           else
  499.             remove_entry s, options[:force]
  500.           end
  501.         end
  502.       rescue SystemCallError
  503.         raise unless options[:force]
  504.       end
  505.     end
  506.   end
  507.   module_function :mv
  508.  
  509.   alias move mv
  510.   module_function :move
  511.  
  512.   OPT_TABLE['mv']   =
  513.   OPT_TABLE['move'] = [:force, :noop, :verbose, :secure]
  514.  
  515.   def rename_cannot_overwrite_file?   #:nodoc:
  516.     /djgpp|cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
  517.   end
  518.   private_module_function :rename_cannot_overwrite_file?
  519.  
  520.   #
  521.   # Options: force noop verbose
  522.   # 
  523.   # Remove file(s) specified in +list+.  This method cannot remove directories.
  524.   # All StandardErrors are ignored when the :force option is set.
  525.   # 
  526.   #   FileUtils.rm %w( junk.txt dust.txt )
  527.   #   FileUtils.rm Dir.glob('*.so')
  528.   #   FileUtils.rm 'NotExistFile', :force => true   # never raises exception
  529.   # 
  530.   def rm(list, options = {})
  531.     fu_check_options options, OPT_TABLE['rm']
  532.     list = fu_list(list)
  533.     fu_output_message "rm#{options[:force] ? ' -f' : ''} #{list.join ' '}" if options[:verbose]
  534.     return if options[:noop]
  535.  
  536.     list.each do |path|
  537.       remove_file path, options[:force]
  538.     end
  539.   end
  540.   module_function :rm
  541.  
  542.   alias remove rm
  543.   module_function :remove
  544.  
  545.   OPT_TABLE['rm']     =
  546.   OPT_TABLE['remove'] = [:force, :noop, :verbose]
  547.  
  548.   #
  549.   # Options: noop verbose
  550.   # 
  551.   # Equivalent to
  552.   #
  553.   #   #rm(list, :force => true)
  554.   #
  555.   def rm_f(list, options = {})
  556.     fu_check_options options, OPT_TABLE['rm_f']
  557.     options = options.dup
  558.     options[:force] = true
  559.     rm list, options
  560.   end
  561.   module_function :rm_f
  562.  
  563.   alias safe_unlink rm_f
  564.   module_function :safe_unlink
  565.  
  566.   OPT_TABLE['rm_f']        =
  567.   OPT_TABLE['safe_unlink'] = [:noop, :verbose]
  568.  
  569.   #
  570.   # Options: force noop verbose secure
  571.   # 
  572.   # remove files +list+[0] +list+[1]... If +list+[n] is a directory,
  573.   # removes its all contents recursively. This method ignores
  574.   # StandardError when :force option is set.
  575.   # 
  576.   #   FileUtils.rm_r Dir.glob('/tmp/*')
  577.   #   FileUtils.rm_r '/', :force => true          #  :-)
  578.   #
  579.   # WARNING: This method causes local vulnerability
  580.   # if one of parent directories or removing directory tree are world
  581.   # writable (including /tmp, whose permission is 1777), and the current
  582.   # process has strong privilege such as Unix super user (root), and the
  583.   # system has symbolic link.  For secure removing, read the documentation
  584.   # of #remove_entry_secure carefully, and set :secure option to true.
  585.   # Default is :secure=>false.
  586.   #
  587.   # NOTE: This method calls #remove_entry_secure if :secure option is set.
  588.   # See also #remove_entry_secure.
  589.   # 
  590.   def rm_r(list, options = {})
  591.     fu_check_options options, OPT_TABLE['rm_r']
  592.     # options[:secure] = true unless options.key?(:secure)
  593.     list = fu_list(list)
  594.     fu_output_message "rm -r#{options[:force] ? 'f' : ''} #{list.join ' '}" if options[:verbose]
  595.     return if options[:noop]
  596.     list.each do |path|
  597.       if options[:secure]
  598.         remove_entry_secure path, options[:force]
  599.       else
  600.         remove_entry path, options[:force]
  601.       end
  602.     end
  603.   end
  604.   module_function :rm_r
  605.  
  606.   OPT_TABLE['rm_r'] = [:force, :noop, :verbose, :secure]
  607.  
  608.   #
  609.   # Options: noop verbose secure
  610.   # 
  611.   # Equivalent to
  612.   #
  613.   #   #rm_r(list, :force => true)
  614.   #
  615.   # WARNING: This method causes local vulnerability.
  616.   # Read the documentation of #rm_r first.
  617.   # 
  618.   def rm_rf(list, options = {})
  619.     fu_check_options options, OPT_TABLE['rm_rf']
  620.     options = options.dup
  621.     options[:force] = true
  622.     rm_r list, options
  623.   end
  624.   module_function :rm_rf
  625.  
  626.   alias rmtree rm_rf
  627.   module_function :rmtree
  628.  
  629.   OPT_TABLE['rm_rf']  =
  630.   OPT_TABLE['rmtree'] = [:noop, :verbose, :secure]
  631.  
  632.   #
  633.   # This method removes a file system entry +path+.  +path+ shall be a
  634.   # regular file, a directory, or something.  If +path+ is a directory,
  635.   # remove it recursively.  This method is required to avoid TOCTTOU
  636.   # (time-of-check-to-time-of-use) local security vulnerability of #rm_r.
  637.   # #rm_r causes security hole when:
  638.   #
  639.   #   * Parent directory is world writable (including /tmp).
  640.   #   * Removing directory tree includes world writable directory.
  641.   #   * The system has symbolic link.
  642.   #
  643.   # To avoid this security hole, this method applies special preprocess.
  644.   # If +path+ is a directory, this method chown(2) and chmod(2) all
  645.   # removing directories.  This requires the current process is the
  646.   # owner of the removing whole directory tree, or is the super user (root).
  647.   #
  648.   # WARNING: You must ensure that *ALL* parent directories are not
  649.   # world writable.  Otherwise this method does not work.
  650.   # Only exception is temporary directory like /tmp and /var/tmp,
  651.   # whose permission is 1777.
  652.   #
  653.   # WARNING: Only the owner of the removing directory tree, or Unix super
  654.   # user (root) should invoke this method.  Otherwise this method does not
  655.   # work.
  656.   #
  657.   # For details of this security vulnerability, see Perl's case:
  658.   #
  659.   #   http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448
  660.   #   http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452
  661.   #
  662.   # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100].
  663.   #
  664.   def remove_entry_secure(path, force = false)
  665.     unless fu_have_symlink?
  666.       remove_entry path, force
  667.       return
  668.     end
  669.     fullpath = File.expand_path(path)
  670.     st = File.lstat(fullpath)
  671.     unless st.directory?
  672.       File.unlink fullpath
  673.       return
  674.     end
  675.     # is a directory.
  676.     parent_st = File.stat(File.dirname(fullpath))
  677.     unless fu_world_writable?(parent_st)
  678.       remove_entry path, force
  679.       return
  680.     end
  681.     unless parent_st.sticky?
  682.       raise ArgumentError, "parent directory is world writable, FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})"
  683.     end
  684.     # freeze tree root
  685.     euid = Process.euid
  686.     File.open(fullpath + '/.') {|f|
  687.       unless fu_stat_identical_entry?(st, f.stat)
  688.         # symlink (TOC-to-TOU attack?)
  689.         File.unlink fullpath
  690.         return
  691.       end
  692.       f.chown euid, -1
  693.       f.chmod 0700
  694.     }
  695.     # ---- tree root is frozen ----
  696.     root = Entry_.new(path)
  697.     root.preorder_traverse do |ent|
  698.       if ent.directory?
  699.         ent.chown euid, -1
  700.         ent.chmod 0700
  701.       end
  702.     end
  703.     root.postorder_traverse do |ent|
  704.       begin
  705.         ent.remove
  706.       rescue
  707.         raise unless force
  708.       end
  709.     end
  710.   rescue
  711.     raise unless force
  712.   end
  713.   module_function :remove_entry_secure
  714.  
  715.   def fu_world_writable?(st)
  716.     (st.mode & 0002) != 0
  717.   end
  718.   private_module_function :fu_world_writable?
  719.  
  720.   def fu_have_symlink?   #:nodoc
  721.     File.symlink nil, nil
  722.   rescue NotImplementedError
  723.     return false
  724.   rescue
  725.     return true
  726.   end
  727.   private_module_function :fu_have_symlink?
  728.  
  729.   def fu_stat_identical_entry?(a, b)   #:nodoc:
  730.     a.dev == b.dev and a.ino == b.ino
  731.   end
  732.   private_module_function :fu_stat_identical_entry?
  733.  
  734.   #
  735.   # This method removes a file system entry +path+.
  736.   # +path+ might be a regular file, a directory, or something.
  737.   # If +path+ is a directory, remove it recursively.
  738.   #
  739.   # See also #remove_entry_secure.
  740.   #
  741.   def remove_entry(path, force = false)
  742.     Entry_.new(path).postorder_traverse do |ent|
  743.       begin
  744.         ent.remove
  745.       rescue
  746.         raise unless force
  747.       end
  748.     end
  749.   rescue
  750.     raise unless force
  751.   end
  752.   module_function :remove_entry
  753.  
  754.   #
  755.   # Removes a file +path+.
  756.   # This method ignores StandardError if +force+ is true.
  757.   #
  758.   def remove_file(path, force = false)
  759.     Entry_.new(path).remove_file
  760.   rescue
  761.     raise unless force
  762.   end
  763.   module_function :remove_file
  764.  
  765.   #
  766.   # Removes a directory +dir+ and its contents recursively.
  767.   # This method ignores StandardError if +force+ is true.
  768.   #
  769.   def remove_dir(path, force = false)
  770.     remove_entry path, force   # FIXME?? check if it is a directory
  771.   end
  772.   module_function :remove_dir
  773.  
  774.   #
  775.   # Returns true if the contents of a file A and a file B are identical.
  776.   # 
  777.   #   FileUtils.compare_file('somefile', 'somefile')  #=> true
  778.   #   FileUtils.compare_file('/bin/cp', '/bin/mv')    #=> maybe false
  779.   #
  780.   def compare_file(a, b)
  781.     return false unless File.size(a) == File.size(b)
  782.     File.open(a, 'rb') {|fa|
  783.       File.open(b, 'rb') {|fb|
  784.         return compare_stream(fa, fb)
  785.       }
  786.     }
  787.   end
  788.   module_function :compare_file
  789.  
  790.   alias identical? compare_file
  791.   alias cmp compare_file
  792.   module_function :identical?
  793.   module_function :cmp
  794.  
  795.   #
  796.   # Returns true if the contents of a stream +a+ and +b+ are identical.
  797.   #
  798.   def compare_stream(a, b)
  799.     bsize = fu_stream_blksize(a, b)
  800.     sa = sb = nil
  801.     while sa == sb
  802.       sa = a.read(bsize)
  803.       sb = b.read(bsize)
  804.       unless sa and sb
  805.         if sa.nil? and sb.nil?
  806.           return true
  807.         end
  808.       end
  809.     end
  810.     false
  811.   end
  812.   module_function :compare_stream
  813.  
  814.   #
  815.   # Options: mode preserve noop verbose
  816.   # 
  817.   # If +src+ is not same as +dest+, copies it and changes the permission
  818.   # mode to +mode+.  If +dest+ is a directory, destination is +dest+/+src+.
  819.   # This method removes destination before copy.
  820.   # 
  821.   #   FileUtils.install 'ruby', '/usr/local/bin/ruby', :mode => 0755, :verbose => true
  822.   #   FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose => true
  823.   # 
  824.   def install(src, dest, options = {})
  825.     fu_check_options options, OPT_TABLE['install']
  826.     fu_output_message "install -c#{options[:preserve] && ' -p'}#{options[:mode] ? (' -m 0%o' % options[:mode]) : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
  827.     return if options[:noop]
  828.     fu_each_src_dest(src, dest) do |s, d|
  829.       unless File.exist?(d) and compare_file(s, d)
  830.         remove_file d, true
  831.         st = File.stat(s) if options[:preserve]
  832.         copy_file s, d
  833.         File.utime st.atime, st.mtime, d if options[:preserve]
  834.         File.chmod options[:mode], d if options[:mode]
  835.       end
  836.     end
  837.   end
  838.   module_function :install
  839.  
  840.   OPT_TABLE['install'] = [:mode, :preserve, :noop, :verbose]
  841.  
  842.   #
  843.   # Options: noop verbose
  844.   # 
  845.   # Changes permission bits on the named files (in +list+) to the bit pattern
  846.   # represented by +mode+.
  847.   # 
  848.   #   FileUtils.chmod 0755, 'somecommand'
  849.   #   FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb)
  850.   #   FileUtils.chmod 0755, '/usr/bin/ruby', :verbose => true
  851.   # 
  852.   def chmod(mode, list, options = {})
  853.     fu_check_options options, OPT_TABLE['chmod']
  854.     list = fu_list(list)
  855.     fu_output_message sprintf('chmod %o %s', mode, list.join(' ')) if options[:verbose]
  856.     return if options[:noop]
  857.     list.each do |path|
  858.       Entry_.new(path).chmod mode
  859.     end
  860.   end
  861.   module_function :chmod
  862.  
  863.   OPT_TABLE['chmod'] = [:noop, :verbose]
  864.  
  865.   #
  866.   # Options: noop verbose force
  867.   # 
  868.   # Changes permission bits on the named files (in +list+)
  869.   # to the bit pattern represented by +mode+.
  870.   # 
  871.   #   FileUtils.chmod_R 0700, "/tmp/app.#{$$}"
  872.   # 
  873.   def chmod_R(mode, list, options = {})
  874.     fu_check_options options, OPT_TABLE['chmod_R']
  875.     list = fu_list(list)
  876.     fu_output_message sprintf('chmod -R%s %o %s',
  877.                               (options[:force] ? 'f' : ''),
  878.                               mode, list.join(' ')) if options[:verbose]
  879.     return if options[:noop]
  880.     list.each do |root|
  881.       Entry_.new(root).traverse do |ent|
  882.         begin
  883.           ent.chmod mode
  884.         rescue
  885.           raise unless options[:force]
  886.         end
  887.       end
  888.     end
  889.   end
  890.   module_function :chmod_R
  891.  
  892.   OPT_TABLE['chmod_R'] = [:noop, :verbose, :force]
  893.  
  894.   #
  895.   # Options: noop verbose
  896.   # 
  897.   # Changes owner and group on the named files (in +list+)
  898.   # to the user +user+ and the group +group+.  +user+ and +group+
  899.   # may be an ID (Integer/String) or a name (String).
  900.   # If +user+ or +group+ is nil, this method does not change
  901.   # the attribute.
  902.   # 
  903.   #   FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby'
  904.   #   FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), :verbose => true
  905.   # 
  906.   def chown(user, group, list, options = {})
  907.     fu_check_options options, OPT_TABLE['chown']
  908.     list = fu_list(list)
  909.     fu_output_message sprintf('chown %s%s',
  910.                               [user,group].compact.join(':') + ' ',
  911.                               list.join(' ')) if options[:verbose]
  912.     return if options[:noop]
  913.     uid = fu_get_uid(user)
  914.     gid = fu_get_gid(group)
  915.     list.each do |path|
  916.       Entry_.new(path).chown uid, gid
  917.     end
  918.   end
  919.   module_function :chown
  920.  
  921.   OPT_TABLE['chown'] = [:noop, :verbose]
  922.  
  923.   #
  924.   # Options: noop verbose force
  925.   # 
  926.   # Changes owner and group on the named files (in +list+)
  927.   # to the user +user+ and the group +group+ recursively.
  928.   # +user+ and +group+ may be an ID (Integer/String) or
  929.   # a name (String).  If +user+ or +group+ is nil, this
  930.   # method does not change the attribute.
  931.   # 
  932.   #   FileUtils.chown_R 'www', 'www', '/var/www/htdocs'
  933.   #   FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', :verbose => true
  934.   # 
  935.   def chown_R(user, group, list, options = {})
  936.     fu_check_options options, OPT_TABLE['chown_R']
  937.     list = fu_list(list)
  938.     fu_output_message sprintf('chown -R%s %s%s',
  939.                               (options[:force] ? 'f' : ''),
  940.                               [user,group].compact.join(':') + ' ',
  941.                               list.join(' ')) if options[:verbose]
  942.     return if options[:noop]
  943.     uid = fu_get_uid(user)
  944.     gid = fu_get_gid(group)
  945.     return unless uid or gid
  946.     list.each do |root|
  947.       Entry_.new(root).traverse do |ent|
  948.         begin
  949.           ent.chown uid, gid
  950.         rescue
  951.           raise unless options[:force]
  952.         end
  953.       end
  954.     end
  955.   end
  956.   module_function :chown_R
  957.  
  958.   OPT_TABLE['chown_R'] = [:noop, :verbose, :force]
  959.  
  960.   begin
  961.     require 'etc'
  962.  
  963.     def fu_get_uid(user)   #:nodoc:
  964.       return nil unless user
  965.       user = user.to_s
  966.       if /\A\d+\z/ =~ user
  967.       then user.to_i
  968.       else Etc.getpwnam(user).uid
  969.       end
  970.     end
  971.     private_module_function :fu_get_uid
  972.  
  973.     def fu_get_gid(group)   #:nodoc:
  974.       return nil unless group
  975.       group = group.to_s
  976.       if /\A\d+\z/ =~ group
  977.       then group.to_i
  978.       else Etc.getgrnam(group).gid
  979.       end
  980.     end
  981.     private_module_function :fu_get_gid
  982.  
  983.   rescue LoadError
  984.     # need Win32 support???
  985.  
  986.     def fu_get_uid(user)   #:nodoc:
  987.       user    # FIXME
  988.     end
  989.     private_module_function :fu_get_uid
  990.  
  991.     def fu_get_gid(group)   #:nodoc:
  992.       group   # FIXME
  993.     end
  994.     private_module_function :fu_get_gid
  995.   end
  996.  
  997.   #
  998.   # Options: noop verbose
  999.   # 
  1000.   # Updates modification time (mtime) and access time (atime) of file(s) in
  1001.   # +list+.  Files are created if they don't exist.
  1002.   # 
  1003.   #   FileUtils.touch 'timestamp'
  1004.   #   FileUtils.touch Dir.glob('*.c');  system 'make'
  1005.   # 
  1006.   def touch(list, options = {})
  1007.     fu_check_options options, OPT_TABLE['touch']
  1008.     list = fu_list(list)
  1009.     created = nocreate = options[:nocreate]
  1010.     t = options[:mtime]
  1011.     if options[:verbose]
  1012.       fu_output_message "touch #{nocreate ? ' -c' : ''}#{t ? t.strftime(' -t %Y%m%d%H%M.%S') : ''}#{list.join ' '}"
  1013.     end
  1014.     return if options[:noop]
  1015.     list.each do |path|
  1016.       created = nocreate
  1017.       begin
  1018.         File.utime(t, t, path)
  1019.       rescue Errno::ENOENT
  1020.         raise if created
  1021.         File.open(path, 'a') {
  1022.           ;
  1023.         }
  1024.         created = true
  1025.         retry if t
  1026.       end
  1027.     end
  1028.   end
  1029.   module_function :touch
  1030.  
  1031.   OPT_TABLE['touch'] = [:noop, :verbose, :mtime, :nocreate]
  1032.  
  1033.   private
  1034.  
  1035.   module StreamUtils_
  1036.     private
  1037.  
  1038.     def fu_windows?
  1039.       /mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
  1040.     end
  1041.  
  1042.     def fu_copy_stream0(src, dest, blksize)   #:nodoc:
  1043.       # FIXME: readpartial?
  1044.       while s = src.read(blksize)
  1045.         dest.write s
  1046.       end
  1047.     end
  1048.  
  1049.     def fu_stream_blksize(*streams)
  1050.       streams.each do |s|
  1051.         next unless s.respond_to?(:stat)
  1052.         size = fu_blksize(s.stat)
  1053.         return size if size
  1054.       end
  1055.       fu_default_blksize()
  1056.     end
  1057.  
  1058.     def fu_blksize(st)
  1059.       s = st.blksize
  1060.       return nil unless s
  1061.       return nil if s == 0
  1062.       s
  1063.     end
  1064.  
  1065.     def fu_default_blksize
  1066.       1024
  1067.     end
  1068.   end
  1069.  
  1070.   include StreamUtils_
  1071.   extend StreamUtils_
  1072.  
  1073.   class Entry_   #:nodoc: internal use only
  1074.     include StreamUtils_
  1075.  
  1076.     def initialize(a, b = nil, deref = false)
  1077.       @prefix = @rel = @path = nil
  1078.       if b
  1079.         @prefix = a
  1080.         @rel = b
  1081.       else
  1082.         @path = a
  1083.       end
  1084.       @deref = deref
  1085.       @stat = nil
  1086.       @lstat = nil
  1087.     end
  1088.  
  1089.     def inspect
  1090.       "\#<#{self.class} #{path()}>"
  1091.     end
  1092.  
  1093.     def path
  1094.       if @path
  1095.         @path.to_str
  1096.       else
  1097.         join(@prefix, @rel)
  1098.       end
  1099.     end
  1100.  
  1101.     def prefix
  1102.       @prefix || @path
  1103.     end
  1104.  
  1105.     def rel
  1106.       @rel
  1107.     end
  1108.  
  1109.     def dereference?
  1110.       @deref
  1111.     end
  1112.  
  1113.     def exist?
  1114.       lstat! ? true : false
  1115.     end
  1116.  
  1117.     def file?
  1118.       s = lstat!
  1119.       s and s.file?
  1120.     end
  1121.  
  1122.     def directory?
  1123.       s = lstat!
  1124.       s and s.directory?
  1125.     end
  1126.  
  1127.     def symlink?
  1128.       s = lstat!
  1129.       s and s.symlink?
  1130.     end
  1131.  
  1132.     def chardev?
  1133.       s = lstat!
  1134.       s and s.chardev?
  1135.     end
  1136.  
  1137.     def blockdev?
  1138.       s = lstat!
  1139.       s and s.blockdev?
  1140.     end
  1141.  
  1142.     def socket?
  1143.       s = lstat!
  1144.       s and s.socket?
  1145.     end
  1146.  
  1147.     def pipe?
  1148.       s = lstat!
  1149.       s and s.pipe?
  1150.     end
  1151.  
  1152.     S_IF_DOOR = 0xD000
  1153.  
  1154.     def door?
  1155.       s = lstat!
  1156.       s and (s.mode & 0xF000 == S_IF_DOOR)
  1157.     end
  1158.  
  1159.     def entries
  1160.       Dir.entries(path())\
  1161.           .reject {|n| n == '.' or n == '..' }\
  1162.           .map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) }
  1163.     end
  1164.  
  1165.     def stat
  1166.       return @stat if @stat
  1167.       if lstat() and lstat().symlink?
  1168.         @stat = File.stat(path())
  1169.       else
  1170.         @stat = lstat()
  1171.       end
  1172.       @stat
  1173.     end
  1174.  
  1175.     def stat!
  1176.       return @stat if @stat
  1177.       if lstat! and lstat!.symlink?
  1178.         @stat = File.stat(path())
  1179.       else
  1180.         @stat = lstat!
  1181.       end
  1182.       @stat
  1183.     rescue SystemCallError
  1184.       nil
  1185.     end
  1186.  
  1187.     def lstat
  1188.       if dereference?
  1189.         @lstat ||= File.stat(path())
  1190.       else
  1191.         @lstat ||= File.lstat(path())
  1192.       end
  1193.     end
  1194.  
  1195.     def lstat!
  1196.       lstat()
  1197.     rescue SystemCallError
  1198.       nil
  1199.     end
  1200.  
  1201.     def chmod(mode)
  1202.       if symlink?
  1203.         File.lchmod mode, path() if have_lchmod?
  1204.       else
  1205.         File.chmod mode, path()
  1206.       end
  1207.     end
  1208.  
  1209.     def chown(uid, gid)
  1210.       if symlink?
  1211.         File.lchown uid, gid, path() if have_lchown?
  1212.       else
  1213.         File.chown uid, gid, path()
  1214.       end
  1215.     end
  1216.  
  1217.     def copy(dest)
  1218.       case
  1219.       when file?
  1220.         copy_file dest
  1221.       when directory?
  1222.         begin
  1223.           Dir.mkdir dest
  1224.         rescue
  1225.           raise unless File.directory?(dest)
  1226.         end
  1227.       when symlink?
  1228.         File.symlink File.readlink(path()), dest
  1229.       when chardev?
  1230.         raise "cannot handle device file" unless File.respond_to?(:mknod)
  1231.         mknod dest, ?c, 0666, lstat().rdev
  1232.       when blockdev?
  1233.         raise "cannot handle device file" unless File.respond_to?(:mknod)
  1234.         mknod dest, ?b, 0666, lstat().rdev
  1235.       when socket?
  1236.         raise "cannot handle socket" unless File.respond_to?(:mknod)
  1237.         mknod dest, nil, lstat().mode, 0
  1238.       when pipe?
  1239.         raise "cannot handle FIFO" unless File.respond_to?(:mkfifo)
  1240.         mkfifo dest, 0666
  1241.       when door?
  1242.         raise "cannot handle door: #{path()}"
  1243.       else
  1244.         raise "unknown file type: #{path()}"
  1245.       end
  1246.     end
  1247.  
  1248.     def copy_file(dest)
  1249.       st = stat()
  1250.       File.open(path(),  'rb') {|r|
  1251.         File.open(dest, 'wb', st.mode) {|w|
  1252.           fu_copy_stream0 r, w, (fu_blksize(st) || fu_default_blksize())
  1253.         }
  1254.       }
  1255.     end
  1256.  
  1257.     def copy_metadata(path)
  1258.       st = lstat()
  1259.       File.utime st.atime, st.mtime, path
  1260.       begin
  1261.         File.chown st.uid, st.gid, path
  1262.       rescue Errno::EPERM
  1263.         # clear setuid/setgid
  1264.         File.chmod st.mode & 01777, path
  1265.       else
  1266.         File.chmod st.mode, path
  1267.       end
  1268.     end
  1269.  
  1270.     def remove
  1271.       if directory?
  1272.         remove_dir1
  1273.       else
  1274.         remove_file
  1275.       end
  1276.     end
  1277.  
  1278.     def remove_dir1
  1279.       platform_support {
  1280.         Dir.rmdir path().sub(%r</\z>, '')
  1281.       }
  1282.     end
  1283.  
  1284.     def remove_file
  1285.       platform_support {
  1286.         File.unlink path
  1287.       }
  1288.     end
  1289.  
  1290.     def platform_support
  1291.       return yield unless fu_windows?
  1292.       first_time_p = true
  1293.       begin
  1294.         yield
  1295.       rescue Errno::ENOENT
  1296.         raise
  1297.       rescue => err
  1298.         if first_time_p
  1299.           first_time_p = false
  1300.           begin
  1301.             File.chmod 0700, path()   # Windows does not have symlink
  1302.             retry
  1303.           rescue SystemCallError
  1304.           end
  1305.         end
  1306.         raise err
  1307.       end
  1308.     end
  1309.  
  1310.     def preorder_traverse
  1311.       stack = [self]
  1312.       while ent = stack.pop
  1313.         yield ent
  1314.         stack.concat ent.entries.reverse if ent.directory?
  1315.       end
  1316.     end
  1317.  
  1318.     alias traverse preorder_traverse
  1319.  
  1320.     def postorder_traverse
  1321.       if directory?
  1322.         entries().each do |ent|
  1323.           ent.postorder_traverse do |e|
  1324.             yield e
  1325.           end
  1326.         end
  1327.       end
  1328.       yield self
  1329.     end
  1330.  
  1331.     private
  1332.  
  1333.     $fileutils_rb_have_lchmod = nil
  1334.  
  1335.     def have_lchmod?
  1336.       # This is not MT-safe, but it does not matter.
  1337.       if $fileutils_rb_have_lchmod == nil
  1338.         $fileutils_rb_have_lchmod = check_have_lchmod?
  1339.       end
  1340.       $fileutils_rb_have_lchmod
  1341.     end
  1342.  
  1343.     def check_have_lchmod?
  1344.       return false unless File.respond_to?(:lchmod)
  1345.       File.lchmod 0
  1346.       return true
  1347.     rescue NotImplementedError
  1348.       return false
  1349.     end
  1350.  
  1351.     $fileutils_rb_have_lchown = nil
  1352.  
  1353.     def have_lchown?
  1354.       # This is not MT-safe, but it does not matter.
  1355.       if $fileutils_rb_have_lchown == nil
  1356.         $fileutils_rb_have_lchown = check_have_lchown?
  1357.       end
  1358.       $fileutils_rb_have_lchown
  1359.     end
  1360.  
  1361.     def check_have_lchown?
  1362.       return false unless File.respond_to?(:lchown)
  1363.       File.lchown nil, nil
  1364.       return true
  1365.     rescue NotImplementedError
  1366.       return false
  1367.     end
  1368.  
  1369.     def join(dir, base)
  1370.       return dir.to_str if not base or base == '.'
  1371.       return base.to_str if not dir or dir == '.'
  1372.       File.join(dir, base)
  1373.     end
  1374.   end   # class Entry_
  1375.  
  1376.   def fu_list(arg)   #:nodoc:
  1377.     [arg].flatten.map {|path| path.to_str }
  1378.   end
  1379.   private_module_function :fu_list
  1380.  
  1381.   def fu_each_src_dest(src, dest)   #:nodoc:
  1382.     fu_each_src_dest0(src, dest) do |s, d|
  1383.       raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d)
  1384.       yield s, d
  1385.     end
  1386.   end
  1387.   private_module_function :fu_each_src_dest
  1388.  
  1389.   def fu_each_src_dest0(src, dest)   #:nodoc:
  1390.     if src.is_a?(Array)
  1391.       src.each do |s|
  1392.         s = s.to_str
  1393.         yield s, File.join(dest, File.basename(s))
  1394.       end
  1395.     else
  1396.       src = src.to_str
  1397.       if File.directory?(dest)
  1398.         yield src, File.join(dest, File.basename(src))
  1399.       else
  1400.         yield src, dest.to_str
  1401.       end
  1402.     end
  1403.   end
  1404.   private_module_function :fu_each_src_dest0
  1405.  
  1406.   def fu_same?(a, b)   #:nodoc:
  1407.     if fu_have_st_ino?
  1408.       st1 = File.stat(a)
  1409.       st2 = File.stat(b)
  1410.       st1.dev == st2.dev and st1.ino == st2.ino
  1411.     else
  1412.       File.expand_path(a) == File.expand_path(b)
  1413.     end
  1414.   rescue Errno::ENOENT
  1415.     return false
  1416.   end
  1417.   private_module_function :fu_same?
  1418.  
  1419.   def fu_have_st_ino?   #:nodoc:
  1420.     not fu_windows?
  1421.   end
  1422.   private_module_function :fu_have_st_ino?
  1423.  
  1424.   def fu_check_options(options, optdecl)   #:nodoc:
  1425.     h = options.dup
  1426.     optdecl.each do |opt|
  1427.       h.delete opt
  1428.     end
  1429.     raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty?
  1430.   end
  1431.   private_module_function :fu_check_options
  1432.  
  1433.   def fu_update_option(args, new)   #:nodoc:
  1434.     if args.last.is_a?(Hash)
  1435.       args[-1] = args.last.dup.update(new)
  1436.     else
  1437.       args.push new
  1438.     end
  1439.     args
  1440.   end
  1441.   private_module_function :fu_update_option
  1442.  
  1443.   @fileutils_output = $stderr
  1444.   @fileutils_label  = ''
  1445.  
  1446.   def fu_output_message(msg)   #:nodoc:
  1447.     @fileutils_output ||= $stderr
  1448.     @fileutils_label  ||= ''
  1449.     @fileutils_output.puts @fileutils_label + msg
  1450.   end
  1451.   private_module_function :fu_output_message
  1452.  
  1453.   #
  1454.   # Returns an Array of method names which have any options.
  1455.   #
  1456.   #   p FileUtils.commands  #=> ["chmod", "cp", "cp_r", "install", ...]
  1457.   #
  1458.   def FileUtils.commands
  1459.     OPT_TABLE.keys
  1460.   end
  1461.  
  1462.   #
  1463.   # Returns an Array of option names.
  1464.   #
  1465.   #   p FileUtils.options  #=> ["noop", "force", "verbose", "preserve", "mode"]
  1466.   #
  1467.   def FileUtils.options
  1468.     OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s }
  1469.   end
  1470.  
  1471.   #
  1472.   # Returns true if the method +mid+ have an option +opt+.
  1473.   #
  1474.   #   p FileUtils.have_option?(:cp, :noop)     #=> true
  1475.   #   p FileUtils.have_option?(:rm, :force)    #=> true
  1476.   #   p FileUtils.have_option?(:rm, :perserve) #=> false
  1477.   #
  1478.   def FileUtils.have_option?(mid, opt)
  1479.     li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}"
  1480.     li.include?(opt)
  1481.   end
  1482.  
  1483.   #
  1484.   # Returns an Array of option names of the method +mid+.
  1485.   #
  1486.   #   p FileUtils.options(:rm)  #=> ["noop", "verbose", "force"]
  1487.   #
  1488.   def FileUtils.options_of(mid)
  1489.     OPT_TABLE[mid.to_s].map {|sym| sym.to_s }
  1490.   end
  1491.  
  1492.   #
  1493.   # Returns an Array of method names which have the option +opt+.
  1494.   #
  1495.   #   p FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"]
  1496.   #
  1497.   def FileUtils.collect_method(opt)
  1498.     OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) }
  1499.   end
  1500.  
  1501.   METHODS = singleton_methods() - %w( private_module_function
  1502.       commands options have_option? options_of collect_method )
  1503.  
  1504.   # 
  1505.   # This module has all methods of FileUtils module, but it outputs messages
  1506.   # before acting.  This equates to passing the <tt>:verbose</tt> flag to
  1507.   # methods in FileUtils.
  1508.   # 
  1509.   module Verbose
  1510.     include FileUtils
  1511.     @fileutils_output  = $stderr
  1512.     @fileutils_label   = ''
  1513.     ::FileUtils.collect_method(:verbose).each do |name|
  1514.       module_eval(<<-EOS, __FILE__, __LINE__ + 1)
  1515.         def #{name}(*args)
  1516.           super(*fu_update_option(args, :verbose => true))
  1517.         end
  1518.         private :#{name}
  1519.       EOS
  1520.     end
  1521.     extend self
  1522.     class << self
  1523.       ::FileUtils::METHODS.each do |m|
  1524.         public m
  1525.       end
  1526.     end
  1527.   end
  1528.  
  1529.   # 
  1530.   # This module has all methods of FileUtils module, but never changes
  1531.   # files/directories.  This equates to passing the <tt>:noop</tt> flag
  1532.   # to methods in FileUtils.
  1533.   # 
  1534.   module NoWrite
  1535.     include FileUtils
  1536.     @fileutils_output  = $stderr
  1537.     @fileutils_label   = ''
  1538.     ::FileUtils.collect_method(:noop).each do |name|
  1539.       module_eval(<<-EOS, __FILE__, __LINE__ + 1)
  1540.         def #{name}(*args)
  1541.           super(*fu_update_option(args, :noop => true))
  1542.         end
  1543.         private :#{name}
  1544.       EOS
  1545.     end
  1546.     extend self
  1547.     class << self
  1548.       ::FileUtils::METHODS.each do |m|
  1549.         public m
  1550.       end
  1551.     end
  1552.   end
  1553.  
  1554.   # 
  1555.   # This module has all methods of FileUtils module, but never changes
  1556.   # files/directories, with printing message before acting.
  1557.   # This equates to passing the <tt>:noop</tt> and <tt>:verbose</tt> flag
  1558.   # to methods in FileUtils.
  1559.   # 
  1560.   module DryRun
  1561.     include FileUtils
  1562.     @fileutils_output  = $stderr
  1563.     @fileutils_label   = ''
  1564.     ::FileUtils.collect_method(:noop).each do |name|
  1565.       module_eval(<<-EOS, __FILE__, __LINE__ + 1)
  1566.         def #{name}(*args)
  1567.           super(*fu_update_option(args, :noop => true, :verbose => true))
  1568.         end
  1569.         private :#{name}
  1570.       EOS
  1571.     end
  1572.     extend self
  1573.     class << self
  1574.       ::FileUtils::METHODS.each do |m|
  1575.         public m
  1576.       end
  1577.     end
  1578.   end
  1579.  
  1580. end
  1581.